1 package org.csstudio.swt.xygraph.linearscale;
2
3 import java.math.BigDecimal;
4 import java.util.ArrayList;
5 import java.util.Calendar;
6 import java.util.Date;
7
8 import org.eclipse.draw2d.Figure;
9 import org.eclipse.draw2d.FigureUtilities;
10 import org.eclipse.draw2d.Graphics;
11 import org.eclipse.draw2d.geometry.Dimension;
12
13
14
15
16 public class LinearScaleTickLabels extends Figure {
17
18 private static final int TICK_LABEL_GAP = 20;
19
20
21 private ArrayList<Double> tickLabelValues;
22
23
24 private ArrayList<String> tickLabels;
25
26
27 private ArrayList<Integer> tickLabelPositions;
28
29
30 private ArrayList<Boolean> tickVisibilities;
31
32
33
34
35 private int tickLabelMaxLength;
36
37
38 private int tickLabelMaxHeight;
39
40 private int gridStepInPixel;
41
42 private LinearScale scale;
43
44
45
46
47
48
49
50 protected LinearScaleTickLabels(LinearScale linearScale) {
51
52 this.scale = linearScale;
53 tickLabelValues = new ArrayList<Double>();
54 tickLabels = new ArrayList<String>();
55 tickLabelPositions = new ArrayList<Integer>();
56 tickVisibilities = new ArrayList<Boolean>();
57
58 setFont(this.scale.getFont());
59 setForegroundColor(this.scale.getForegroundColor());
60 }
61
62
63
64
65
66
67
68 protected void update(int length) {
69 tickLabelValues.clear();
70 tickLabels.clear();
71 tickLabelPositions.clear();
72
73
74 if (scale.isLogScaleEnabled()) {
75 updateTickLabelForLogScale(length);
76 }else {
77 updateTickLabelForLinearScale(length);
78 }
79
80 updateTickVisibility();
81 updateTickLabelMaxLengthAndHeight();
82 }
83
84
85
86
87
88
89
90 private void updateTickLabelForLogScale(int length) {
91 double min = scale.getRange().getLower();
92 double max = scale.getRange().getUpper();
93 if(min <= 0 || max <= 0)
94 throw new IllegalArgumentException(
95 "the range for log scale must be in positive range");
96 boolean minBigger = max < min;
97
98
99
100
101 int digitMin = (int) Math.ceil(Math.log10(min));
102 int digitMax = (int) Math.ceil(Math.log10(max));
103
104 final BigDecimal MIN = new BigDecimal(new Double(min).toString());
105 BigDecimal tickStep = pow(10, digitMin - 1);
106 BigDecimal firstPosition;
107
108
109 if (MIN.remainder(tickStep).doubleValue() <= 0) {
110 firstPosition = MIN.subtract(MIN.remainder(tickStep));
111 } else {
112 if(minBigger)
113 firstPosition = MIN.subtract(MIN.remainder(tickStep));
114 else
115 firstPosition = MIN.subtract(MIN.remainder(tickStep)).add(tickStep);
116 }
117
118
119 boolean minDateAdded =false;
120 if(MIN.compareTo(firstPosition) == (minBigger? 1:-1) ) {
121 tickLabelValues.add(min);
122 if (scale.isDateEnabled()) {
123 Date date = new Date((long) MIN.doubleValue());
124 tickLabels.add(scale.format(date, true));
125 minDateAdded = true;
126 } else {
127 tickLabels.add(scale.format(MIN.doubleValue()));
128 }
129 tickLabelPositions.add(scale.getMargin());
130 }
131
132 for (int i = digitMin; minBigger? i>=digitMax : i <=digitMax; i+=minBigger?-1:1) {
133 if(Math.abs(digitMax - digitMin) > 20){
134 BigDecimal v = pow(10,i);
135 if(v.doubleValue() > max)
136 break;
137 if (scale.isDateEnabled()) {
138 Date date = new Date((long) v.doubleValue());
139 tickLabels.add(scale.format(date, i==digitMin && !minDateAdded));
140 } else {
141 tickLabels.add(scale.format(v.doubleValue()));
142 }
143 tickLabelValues.add(v.doubleValue());
144
145 int tickLabelPosition = (int) ((Math.log10(v.doubleValue()) - Math
146 .log10(min))
147 / (Math.log10(max) - Math.log10(min)) * length)
148 + scale.getMargin();
149 tickLabelPositions.add(tickLabelPosition);
150 }else{
151 for (BigDecimal j = firstPosition; minBigger? j.doubleValue() >= pow(10, i-1)
152 .doubleValue() : j.doubleValue() <= pow(10, i).doubleValue(); j = minBigger? j.subtract(tickStep) : j.add(tickStep)) {
153 if (minBigger? j.doubleValue() < max : j.doubleValue() > max) {
154 break;
155 }
156
157 if (scale.isDateEnabled()) {
158 Date date = new Date((long) j.doubleValue());
159 tickLabels.add(scale.format(date, j==firstPosition && !minDateAdded));
160 } else {
161 tickLabels.add(scale.format(j.doubleValue()));
162 }
163 tickLabelValues.add(j.doubleValue());
164
165 int tickLabelPosition = (int) ((Math.log10(j.doubleValue()) - Math
166 .log10(min))
167 / (Math.log10(max) - Math.log10(min)) * length)
168 + scale.getMargin();
169 tickLabelPositions.add(tickLabelPosition);
170 }
171 tickStep = minBigger? tickStep.divide(pow(10,1)) : tickStep.multiply(pow(10, 1));
172 firstPosition = minBigger? pow(10,i-1) : tickStep.add(pow(10, i));
173 }
174 }
175
176
177 if(minBigger? max < tickLabelValues.get(tickLabelValues.size()-1)
178 : max > tickLabelValues.get(tickLabelValues.size()-1)) {
179 tickLabelValues.add(max);
180 if (scale.isDateEnabled()) {
181 Date date = new Date((long) max);
182 tickLabels.add(scale.format(date, true));
183 } else {
184 tickLabels.add(scale.format(max));
185 }
186 tickLabelPositions.add(scale.getMargin() + length);
187 }
188 }
189
190
191
192
193
194
195
196
197
198 private void updateTickLabelForLinearScale(int length) {
199 double min = scale.getRange().getLower();
200 double max = scale.getRange().getUpper();
201 BigDecimal gridStepBigDecimal = getGridStep(length, min, max);
202 gridStepInPixel = (int) (length * gridStepBigDecimal.doubleValue()/(max - min));
203 updateTickLabelForLinearScale(length, gridStepBigDecimal);
204 }
205
206
207
208
209
210
211
212
213
214 private void updateTickLabelForLinearScale(int length, BigDecimal tickStep) {
215 double min = scale.getRange().getLower();
216 double max = scale.getRange().getUpper();
217
218 boolean minBigger = max < min;
219
220 final BigDecimal MIN = new BigDecimal(new Double(min).toString());
221 BigDecimal firstPosition;
222
223
224
225 if (MIN.remainder(tickStep).doubleValue() <= 0) {
226
227 firstPosition = MIN.subtract(MIN.remainder(tickStep));
228 } else {
229
230 firstPosition = MIN.subtract(MIN.remainder(tickStep)).add(tickStep);
231 }
232
233
234 if (scale.isDateEnabled()) {
235 BigDecimal zeroOclock = firstPosition.subtract(new BigDecimal(
236 new Double(3600000).toString()));
237 if (MIN.compareTo(zeroOclock) == -1) {
238 firstPosition = zeroOclock;
239 }
240 }
241
242
243 int r = minBigger? 1 : -1;
244 boolean minDateAdded = false;
245 if(MIN.compareTo(firstPosition) == r ) {
246 tickLabelValues.add(min);
247 if (scale.isDateEnabled()) {
248 Date date = new Date((long) MIN.doubleValue());
249 tickLabels.add(scale.format(date, true));
250 minDateAdded = true;
251 } else {
252 tickLabels.add(scale.format(MIN.doubleValue()));
253 }
254 tickLabelPositions.add(scale.getMargin());
255 }
256
257 for (BigDecimal b = firstPosition; max >= min ? b.doubleValue() < max : b.doubleValue() >max; b = b
258 .add(tickStep)) {
259 if (scale.isDateEnabled()) {
260 Date date = new Date((long) b.doubleValue());
261 tickLabels.add(scale.format(date, b==firstPosition && !minDateAdded));
262 } else {
263 tickLabels.add(scale.format(b.doubleValue()));
264 }
265 tickLabelValues.add(b.doubleValue());
266
267 int tickLabelPosition = (int) ((b.doubleValue() - min)
268 / (max - min) * length) + scale.getMargin();
269
270 tickLabelPositions.add(tickLabelPosition);
271 }
272
273
274
275
276 tickLabelValues.add(max);
277 if (scale.isDateEnabled()) {
278 Date date = new Date((long) max);
279 tickLabels.add(scale.format(date, true));
280 } else {
281 tickLabels.add(scale.format(max));
282 }
283 tickLabelPositions.add(scale.getMargin() + length);
284
285
286 }
287
288
289
290
291 private void updateTickVisibility() {
292
293
294 tickVisibilities.clear();
295 for (int i = 0; i < tickLabelPositions.size(); i++) {
296 tickVisibilities.add(Boolean.TRUE);
297 }
298
299 if (tickLabelPositions.size() == 0) {
300 return;
301 }
302
303
304 int previousPosition = 0;
305 String previousLabel = null;
306 for (int i = 0; i < tickLabelPositions.size(); i++) {
307
308
309 boolean hasSpaceToDraw = true;
310 if (i != 0) {
311 hasSpaceToDraw = hasSpaceToDraw(previousPosition,
312 tickLabelPositions.get(i), previousLabel, tickLabels.get(i));
313 }
314
315
316 String currentLabel = tickLabels.get(i);
317 boolean isRepeatSameTickAndNotEnd = currentLabel.equals(previousLabel) &&
318 (i!=0 && i!=tickLabelPositions.size()-1);
319
320
321 boolean isMajorTickOrEnd = true;
322 if (scale.isLogScaleEnabled()) {
323 isMajorTickOrEnd = isMajorTick(tickLabelValues.get(i))
324 || i==0 || i==tickLabelPositions.size()-1;
325 }
326
327 if (!hasSpaceToDraw || isRepeatSameTickAndNotEnd || !isMajorTickOrEnd) {
328 tickVisibilities.set(i, Boolean.FALSE);
329 } else {
330 previousPosition = tickLabelPositions.get(i);
331 previousLabel = currentLabel;
332 }
333 }
334 }
335
336
337
338
339
340
341
342
343
344 private boolean isMajorTick(double tickValue) {
345 if (!scale.isLogScaleEnabled()) {
346 return true;
347 }
348
349 if (Math.log10(tickValue) % 1 == 0) {
350 return true;
351 }
352
353 return false;
354 }
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369 private boolean hasSpaceToDraw(int previousPosition, int tickLabelPosition,
370 String previousTickLabel, String tickLabel) {
371 Dimension tickLabelSize = FigureUtilities.getTextExtents(tickLabel, scale.getFont());
372 Dimension previousTickLabelSize = FigureUtilities.getTextExtents(previousTickLabel, scale.getFont());
373 int interval = tickLabelPosition - previousPosition;
374 int textLength = (int) (scale.isHorizontal() ? (tickLabelSize.width/2.0 + previousTickLabelSize.width/2.0)
375 : tickLabelSize.height);
376 boolean noLapOnPrevoius = true;
377
378 boolean noLapOnEnd = true;
379 if(tickLabelPosition != tickLabelPositions.get(tickLabelPositions.size() - 1)){
380 noLapOnPrevoius = interval > (textLength+TICK_LABEL_GAP);
381 Dimension endTickLabelSize = FigureUtilities.getTextExtents(
382 tickLabels.get(tickLabels.size()-1), scale.getFont());
383 interval = tickLabelPositions.get(tickLabelPositions.size() - 1) - tickLabelPosition;
384 textLength = (int) (scale.isHorizontal() ? (tickLabelSize.width/2.0 + endTickLabelSize.width/2.0)
385 : tickLabelSize.height);
386 noLapOnEnd = interval > textLength+TICK_LABEL_GAP;
387 }
388 return noLapOnPrevoius && noLapOnEnd;
389 }
390
391
392
393
394 private void updateTickLabelMaxLengthAndHeight() {
395 int maxLength = 0;
396 int maxHeight = 0;
397 for (int i = 0; i < tickLabels.size(); i++) {
398 if (tickVisibilities.size() > i && tickVisibilities.get(i) == true) {
399 Dimension p = FigureUtilities.getTextExtents(tickLabels.get(i), scale.getFont());
400 if (tickLabels.get(0).startsWith("-") && !tickLabels.get(i).startsWith("-")) {
401 p.width += FigureUtilities.getTextExtents("-", getFont()).width;
402 }
403 if (p.width > maxLength) {
404 maxLength = p.width;
405 }
406 if(p.height > maxHeight){
407 maxHeight = p.height;
408 }
409 }
410 }
411 tickLabelMaxLength = maxLength;
412 tickLabelMaxHeight = maxHeight;
413 }
414
415
416
417
418
419
420
421
422
423
424
425 private BigDecimal pow(double base, int expornent) {
426 BigDecimal value;
427 if (expornent > 0) {
428 value = new BigDecimal(new Double(base).toString()).pow(expornent);
429 } else {
430 value = BigDecimal.ONE.divide(new BigDecimal(new Double(base)
431 .toString()).pow(-expornent));
432 }
433 return value;
434 }
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449 private BigDecimal getGridStep(int lengthInPixels, double min, double max) {
450 if((int) scale.getMajorGridStep() != 0) {
451 return new BigDecimal(scale.getMajorGridStep());
452 }
453
454 if (lengthInPixels <= 0) {
455 lengthInPixels = 1;
456 }
457 boolean minBigger = false;
458 if (min >= max) {
459 if(max == min)
460 max ++;
461 else{
462 minBigger = true;
463 double swap = min;
464 min = max;
465 max= swap;
466 }
467
468 }
469
470 double length = Math.abs(max - min);
471 double majorTickMarkStepHint = scale.getMajorTickMarkStepHint();
472 if(majorTickMarkStepHint > lengthInPixels)
473 majorTickMarkStepHint = lengthInPixels;
474
475
476 double gridStepHint = length / lengthInPixels
477 * majorTickMarkStepHint;
478
479
480 if(scale.isDateEnabled()) {
481
482
483 long timeStep;
484 if(max-min<10000)
485 timeStep=1l;
486 else if(max - min < 60000)
487 timeStep = 1000l;
488 else if(max - min < 600000)
489 timeStep= 10000l;
490 else if (max -min < 6400000)
491 timeStep = 60000l;
492 else if (max -min < 43200000)
493 timeStep = 600000l;
494 else if (max -min < 86400000)
495 timeStep = 1800000l;
496 else if (max - min < 604800000)
497 timeStep = 3600000l;
498 else
499 timeStep = 86400000l;
500
501 if (scale.getTimeUnit() == Calendar.SECOND) {
502 timeStep = 1000l;
503 } else if (scale.getTimeUnit() == Calendar.MINUTE) {
504 timeStep = 60000l;
505 }else if (scale.getTimeUnit() == Calendar.HOUR_OF_DAY) {
506 timeStep = 3600000l;
507 }else if (scale.getTimeUnit() == Calendar.DATE) {
508 timeStep = 86400000l;
509 }else if (scale.getTimeUnit() == Calendar.MONTH) {
510 timeStep = 30l*86400000l;
511 }else if (scale.getTimeUnit() == Calendar.YEAR) {
512 timeStep = 365l*86400000l;
513 }
514 double temp = gridStepHint + (timeStep - gridStepHint%timeStep);
515 if(minBigger)
516 temp = -temp;
517 return new BigDecimal(temp);
518 }
519
520
521 double mantissa = gridStepHint;
522 int exponent = 0;
523 if (mantissa < 1) {
524 if(mantissa != 0)
525 while (mantissa < 1) {
526 mantissa *= 10.0;
527 exponent--;
528 }
529 } else {
530 while (mantissa >= 10) {
531 mantissa /= 10.0;
532 exponent++;
533 }
534 }
535
536 BigDecimal gridStep;
537 if (mantissa > 7.5) {
538 gridStep = BigDecimal.TEN.multiply(pow(10, exponent));
539 } else if (mantissa > 3.5) {
540 gridStep = new BigDecimal(new Double(5).toString()).multiply(pow(
541 10, exponent));
542 } else if (mantissa > 1.5) {
543 gridStep = new BigDecimal(new Double(2).toString()).multiply(pow(
544 10, exponent));
545 } else {
546 gridStep = pow(10, exponent);
547 }
548 if(minBigger)
549 gridStep = gridStep.negate();
550 return gridStep;
551 }
552
553
554
555
556
557
558 public ArrayList<Integer> getTickLabelPositions() {
559 return tickLabelPositions;
560 }
561
562 @Override
563 protected void paintClientArea(Graphics graphics) {
564 graphics.translate(bounds.x, bounds.y);
565 if (scale.isHorizontal()) {
566 drawXTick(graphics);
567 } else {
568 drawYTick(graphics);
569 }
570
571 super.paintClientArea(graphics);
572 };
573
574
575
576
577
578
579
580 private void drawXTick(Graphics grahics) {
581
582 grahics.setFont(scale.getFont());
583 for (int i = 0; i < tickLabelPositions.size(); i++) {
584 if (tickVisibilities.get(i) == true) {
585 String text = tickLabels.get(i);
586 int fontWidth = FigureUtilities.getTextExtents(text, getFont()).width;
587 int x = (int) Math.ceil(tickLabelPositions.get(i) - fontWidth / 2.0);
588 grahics.drawText(text, x, 0);
589 }
590 }
591 }
592
593
594
595
596
597
598
599 private void drawYTick(Graphics grahpics) {
600
601 grahpics.setFont(scale.getFont());
602 int fontHeight = tickLabelMaxHeight;
603 for (int i = 0; i < tickLabelPositions.size(); i++) {
604 if (tickVisibilities.size() == 0 || tickLabels.size() == 0) {
605 break;
606 }
607
608 if (tickVisibilities.get(i) == true) {
609 String text = tickLabels.get(i);
610 int x = 0;
611 if (tickLabels.get(0).startsWith("-") && !text.startsWith("-")) {
612 x += FigureUtilities.getTextExtents("-", getFont()).width;
613 }
614 int y = (int) Math.ceil(scale.getLength() - tickLabelPositions.get(i)
615 - fontHeight / 2.0);
616 grahpics.drawText(text, x, y);
617 }
618 }
619 }
620
621
622
623
624 public int getTickLabelMaxLength() {
625 return tickLabelMaxLength;
626 }
627
628
629
630
631 public int getTickLabelMaxHeight() {
632 return tickLabelMaxHeight;
633 }
634
635
636
637
638 public ArrayList<Boolean> getTickVisibilities() {
639 return tickVisibilities;
640 }
641
642
643
644
645 public int getGridStepInPixel() {
646 return gridStepInPixel;
647 }
648
649 }